package org.jsea.meta.api.service;

import org.jsea.meta.api.bean.*;
import org.jsea.meta.api.service.fmt.MetaApiFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * --.
 * *
 * author   Jiang.hy
 * create   2022-04-09
 */
@ConditionalOnBean(value = MetaApiApplicationContext.class)
@Service(value = "MetaApiService")
public class MetaApiService {

    private final MetaApiProperties properties;
    private final MetaApiProcessor processor;
    private final MetaApiExecutor executor;
    private final ApplicationContext application;
    public IMetaApiHandler result;

    public MetaApiService(MetaApiProperties properties, MetaApiProcessor processor, MetaApiExecutor executor, ApplicationContext application) {
        this.properties = properties;
        this.processor = processor;
        this.executor = executor;
        this.application = application;
        Class<? extends IMetaApiHandler> rc = properties.getResult();
        if (rc == null) {
            result = new IMetaApiHandler() {
            };
        } else {
            try {
                result = application.getBean(rc);
            } catch (Exception ignored) {
                result = BeanUtils.instantiateClass(rc);
            }
        }
    }

    public Map<String, Object> get(MetaApiGetRequest request, HttpServletRequest req, HttpServletResponse res) {

        MetaApiDefinitionBean bean = processor.generate(request);
        applyInterceptor(bean.getTi().getApply(), i -> i.select(bean, req, res));

        Map<String, Object> all = executor.queryOne(new MetaApiQueryHandler(bean, properties.getQuery()));
        if (all.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, MetaApiFormat> format = bean.getFormat();
        Map<String, Object> result = processor.fmtValue(all, format);

        request.getJoin().forEach((k, val) -> {
            MetaApiDefinitionBean sv = processor.generateGetjoin(result, k, val, format);
            if (sv == null) {
                return;
            }
            if (Objects.equals(sv.getSize(), 0)) {
                result.put(sv.getAlias(), sv.isObj() ? null : Collections.emptyList());
                return;
            }
            applyInterceptor(sv.getTi().getApply(), i -> i.select(bean, req, res));
            if (sv.isObj()) {
                sv.setSize(1);
            }
            List<Map<String, Object>> query = executor.query(new MetaApiQueryHandler(sv, properties.getQuery()));
            if (query.isEmpty()) {
                result.put(sv.getAlias(), sv.isObj() ? null : Collections.emptyList());
                return;
            }
            if (sv.isObj()) {
                result.put(sv.getAlias(), query.getFirst());
            } else {
                if (sv.isSingle()) {
                    result.put(sv.getAlias(), maptolist(sv, query));
                } else {
                    result.put(sv.getAlias(), query);
                }
            }
        });

        return result;
    }

    private void applyInterceptor(List<Class<? extends MetaApiInterceptor>> interceptor,
                                  Consumer<MetaApiInterceptor> consumer) {
        if (interceptor == null) {
            return;
        }
        for (Class<? extends MetaApiInterceptor> clazz : interceptor) {
            MetaApiInterceptor abi = application.getBean(clazz);
            consumer.accept(abi);
        }
    }

    private List<Object> maptolist(MetaApiDefinitionBean bean, List<Map<String, Object>> all) {
        List<Object> list = new ArrayList<>(all.size());
        MetaApiField tf = bean.getColumn().getFirst();
        String name = tf.getAlias() == null ? tf.getName() : tf.getAlias();
        for (Map<String, Object> map : all) {
            list.add(map.get(name));
        }
        return list;
    }

    public Map<String, Object> find(MetaApiFindRequest request, HttpServletRequest req, HttpServletResponse res) {

        MetaApiDefinitionBean bean = processor.generate(request);
        applyInterceptor(bean.getTi().getApply(), i -> i.select(bean, req, res));
        Map<String, Object> map = new HashMap<>();

        /* [ jiang.hy 2024-02-21, 17:4, 周三 ]
         . 不传page，认为不需要分页
         在需要分页的场景，page必然不是null
         -                                  */
        if (request.getPage() == null || request.getPage() == 0) {
            bean.setPage(1);
        } else {
            MetaApiQueryHandler handler = new MetaApiQueryHandler(bean, properties.getQuery());
            int count = executor.queryCount(handler.countSql(), handler.getParam(), bean.getFrom());
            map.put("total", count);
            if (count == 0 || Objects.equals(request.getSize(), 0)) {
                map.put("rows", Collections.emptyList());
                return map;
            }
        }
        List<Map<String, Object>> all = executor.query(new MetaApiQueryHandler(bean, properties.getQuery()));
        if (all.isEmpty()) {
            map.put("rows", Collections.emptyList());
            return map;
        }

        Map<String, MetaApiFormat> format = bean.getFormat();
        for (Map<String, Object> data : all) {
            processor.fmtValue(data, format);
        }
        request.getJoin().forEach((k, val) -> {
            MetaApiDefinitionBean sv = processor.generateFindjoin(all, k, val, format);
            if (Objects.equals(sv.getSize(), 0)) {
                all.forEach(m -> m.put(sv.getAlias(), sv.isObj() ? null : Collections.emptyList()));
                return;
            }
            applyInterceptor(sv.getTi().getApply(), i -> i.select(bean, req, res));
            MetaApiQueryHandler config = new MetaApiQueryHandler(sv, properties.getQuery());
            List<Map<String, Object>> query = executor.query(config);
            if (query.isEmpty()) {
                all.forEach(m -> m.put(sv.getAlias(), sv.isObj() ? null : Collections.emptyList()));
                return;
            }
            //先格式化
            query.forEach(datum -> processor.fmtValue(datum, format));
            List<KV<String, String>> rels = sv.getRelations();
            for (Map<String, Object> box : all) {
                //关联匹配结果
                Stream<Map<String, Object>> stream = query.stream().filter(joins -> {
                    for (KV<String, String> rel : rels) {
                        if (processor.compare(processor.findValue(box, rel.key()), joins.get(rel.val()))) {
                            continue;
                        }
                        return false;
                    }
                    return true;
                });
                if (sv.isObj()) {
                    box.put(sv.getAlias(), stream.findFirst().orElse(null));
                } else {
                    List<Map<String, Object>> collect = stream.collect(Collectors.toList());
                    if (sv.isSingle()) {
                        box.put(sv.getAlias(), maptolist(sv, collect));
                    } else {
                        box.put(sv.getAlias(), collect);
                    }
                }
            }
        });
        if (bean.isSingle() && request.getJoin().isEmpty()) {
            map.put("rows", maptolist(bean, all));
        } else {
            map.put("rows", all);
        }
        return map;
    }

    public Object del(MetaApiDelRequest request, HttpServletRequest req, HttpServletResponse res) {
        MetaApiDefinitionBean bean = processor.generate(request);
        applyInterceptor(bean.getTi().getApply(), i -> i.delete(bean, req, res));
        MetaApiDelHandler handler = new MetaApiDelHandler(bean);
        return executor.executeUpdate(handler.sql(), handler.getParam(), bean.getFrom());
    }

    public Integer edit(MetaApiUpdateRequest request, HttpServletRequest req, HttpServletResponse res) {
        MetaApiDefinitionBean bean = processor.generate(request);
        Map<String, Object> data = bean.getData();
        applyInterceptor(bean.getTi().getApply(), i -> i.update(bean, req, res));
        bean.getFormat().forEach((fk, fmt) -> {
            Object o = data.get(fk);
            if (o == null) {
                return;
            }
            data.put(fk, fmt.parse(o.toString()));
        });
        MetaApiUpdateHandler handler = new MetaApiUpdateHandler(bean);
        return executor.executeUpdate(handler.sql(), handler.getParam(), bean.getFrom());
    }

    @SuppressWarnings("unchecked")
    public Object save(MetaApiSaveRequest request, HttpServletRequest req, HttpServletResponse res) {
        Object obj = request.getData();
        if (obj == null)
            throw new IllegalArgumentException("data is null");
        MetaApiDefinitionBean bean = processor.generate(request);
        List<Map<String, Object>> datas;
        if (obj instanceof List) {
            datas = (List<Map<String, Object>>) obj;
        } else {
            datas = Collections.singletonList((Map<String, Object>) obj);
            bean.setObj(true);
        }
        if (datas.isEmpty())
            throw new IllegalArgumentException("data is null.");

        /* [ jiang.hy 2024-02-23, 15:25, 周五 ]
         . 使用List，保证插入顺序不变
         -                                  */
        List<KV<List<MetaApiField>, Collection<Object>>> tds = new ArrayList<>(datas.size());
        applyInterceptor(bean.getTi().getApply(), i -> i.insert(bean, req, res, datas));
        Map<Collection<String>, List<MetaApiField>> cache = new HashMap<>();
        for (Map<String, Object> data : datas) {
            bean.getFormat().forEach((fk, fmt) -> {
                Object o = data.get(fk);
                if (o == null) {
                    return;
                }
                data.put(fk, fmt.parse(o.toString()));
            });
            //LinkedHashMap, KeySet 和value都是有序的
            List<MetaApiField> fields = cache.get(data.keySet());
            if (fields == null) {
                fields = processor.column(data.keySet());
                cache.put(data.keySet(), fields);
            }
            tds.add(new KV<>(fields, data.values()));
        }
        if (request.isAsync()) {
            executor.doBatchInsertAsync(bean, tds);
            return bean.isObj() ? null : Collections.emptyList();
        }
        List<Object> values = executor.doBatchInsert(bean, tds);
        return bean.isObj() ? values.getFirst() : values;
    }
}
